package cn.thornbird.orgsync.helper;

import cn.thornbird.orgsync.entity.status.AppType;
import cn.thornbird.orgsync.helper.converter.FeishuConverter;
import cn.thornbird.orgsync.model.AppUserData;
import cn.thornbird.orgsync.model.TreeData;
import cn.thornbird.orgsync.model.feishu.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import java.util.*;
import java.util.stream.Collectors;

public class FeishuHelper extends AppHelper<UserList.User> {

    public static final String API_HOST = "https://open.feishu.cn/open-apis";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal">自建应用获取 tenant_access_token</a>
     */
    public static final String GET_TOKEN_PATH = "/auth/v3/tenant_access_token/internal";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/tenant-v2/query">获取企业信息</a>
     */
    public static final String GET_TENANT_INFO = "/tenant/v2/tenant/query";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/department/children">获取子部门列表</a>
     */
    public static final String GET_DEPARTMENT_LIST_PATH = "/contact/v3/departments/{department_id}/children?" +
            "user_id_type=user_id&department_id_type=department_id&fetch_child=true&page_size=50&page_token={page_token}";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/find_by_department">获取部门直属用户列表</a>
     */
    public static final String GET_USER_LIST_PATH = "/contact/v3/users/find_by_department?" +
            "user_id_type=user_id&department_id_type=department_id&page_size=50&" +
            "department_id={department_id}&page_token={page_token}";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/get">获取单个用户信息</a>
     */
    public static final String GET_USER_PATH = "/contact/v3/users/{user_id}?" +
            "user_id_type=user_id&department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/batch_get_id">通过手机号或邮箱获取用户ID</a>
     */
    public static final String GET_USER_ID_PATH = "/contact/v3/users/batch_get_id?user_id_type=user_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/department/create">创建部门</a>
     */
    public static final String CREATE_DEPARTMENT_PATH = "/contact/v3/departments?" +
            "user_id_type=user_id&department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/department/patch">修改部门部分信息</a>
     */
    public static final String UPDATE_DEPARTMENT_PATH = "/contact/v3/departments/{department_id}?" +
            "user_id_type=user_id&department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/department/update_department_id">更新部门ID</a>
     */
    public static final String UPDATE_DEPARTMENT_ID_PATH = "/contact/v3/departments/{department_id}/" +
            "update_department_id?department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/department/delete">删除部门</a>
     */
    public static final String DELETE_DEPARTMENT_PATH = "/contact/v3/departments/{department_id}?" +
            "department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/create">创建用户</a>
     */
    public static final String CREATE_USER_PATH = "/contact/v3/users?" +
            "user_id_type=user_id&department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/patch">修改用户部分信息</a>
     */
    public static final String UPDATE_USER_PATH = "/contact/v3/users/{user_id}?" +
            "user_id_type=user_id&department_id_type=department_id";

    /**
     * <a href="https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/update_user_id">更新用户ID</a>
     */
    public static final String UPDATE_USER_ID_PATH = "/contact/v3/users/{user_id}/update_user_id?user_id_type=user_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/delete">删除用户</a>
     */
    public static final String DELETE_USER_PATH = "/contact/v3/users/{user_id}?user_id_type=user_id";

    /**
     * <a href="https://open.feishu.cn/document/server-docs/contact-v3/user/resurrect">恢复已删除用户</a>
     * 恢复已删除用户接口仅适用于飞书商业专业版、商业旗舰版、企业标准版、企业专业版、企业旗舰版
     */
    public static final String RESURRECT_USER_PATH = "/contact/v3/users/{user_id}/resurrect?" +
            "user_id_type=user_id&department_id_type=department_id";

    @Override
    public void initialize(RedisTemplate<String, String> redisTemplate, String certificateText) {
        super.initialize(redisTemplate, certificateText);
        maxFrequency = 50;
        handleBadRequestError = true;
    }

    @Override
    public String getToken() {
        return new AccessTokenFactory().getAccessToken(AppType.Feishu, certificateText).getToken(redisTemplate);
    }

    @Override
    public List<TreeData> getOrganizationTree() {
        List<TreeData> treeDataList = new ArrayList<>();
        TenantInfo tenantInfo = getTenantInfo();
        if (tenantInfo == null || !tenantInfo.isSuccess()) {
           return treeDataList;
        }

        TreeData item = new TreeData();
        item.setKey("0");
        item.setTitle(tenantInfo.getData().getTenant().getName());
        treeDataList.add(item);
        DepartmentList departmentList = getDepartmentList(null);
        if (departmentList.isSuccess() && departmentList.getData().getItems() != null) {
            List<DepartmentList.Department> departments = departmentList.getData().getItems().stream().sorted(
                    Comparator.comparing(DepartmentList.Department::getParentDepartmentId).thenComparing(
                            DepartmentList.Department::getOrder)
            ).collect(Collectors.toList());
            generateOrganizationTree(treeDataList, departments, item);
        }
        return treeDataList;
    }

    @Override
    public List<AppUserData> getUsersByDepartmentId(String departmentId) {
        List<AppUserData> users = new ArrayList<>();
        UserList userList = getUserList(departmentId, false);
        if (userList.isSuccess() && userList.getData().getItems() != null) {
            FeishuConverter converter = new FeishuConverter();
            for (UserList.User user : userList.getData().getItems()) {
                users.add(converter.parseUser(user));
            }
        }
        return users;
    }

    @Override
    public AppUserData findAppUser(AppUserData sourceUser) {
        AppUserData targetUser = null;
        UserList.User user = getUser(UserAttr.MOBILE, sourceUser.getMobile());
        if (user == null) {
            user = getUser(UserAttr.EMAIL, sourceUser.getEmail());
        }
        if (user == null) {
            user = getUser(UserAttr.EMPLOYEE_NO, sourceUser.getEmployeeNo());
        }
        if (user == null) {
            user = getUser(UserAttr.NAME, sourceUser.getName());
        }
        if (user != null) {
            targetUser = new FeishuConverter().parseUser(user);
        }
        return targetUser;
    }

    @Override
    public AppUserData findAppUserById(String id) {
        AppUserData targetUser = null;
        UserList.User user = getUser(UserAttr.ID, id);
        if (user != null) {
            targetUser = new FeishuConverter().parseUser(user);
        }
        return targetUser;
    }

    public TenantInfo getTenantInfo() {
        return doGet(GET_TENANT_INFO, null, getToken(), TenantInfo.class);
    }

    public DepartmentList getDepartmentList(String departmentId) {
        return getDepartmentList(getToken(), departmentId, null, null);
    }

    public List<String> getDepartmentIds(String departmentId) {
        DepartmentList departmentList = getDepartmentList(departmentId);
        return getDepartmentIds(departmentId, departmentList);
    }

    public List<String> getDepartmentIds(String departmentId, DepartmentList departmentList) {
        List<String> departmentIds = new ArrayList<>();
        departmentId = StringUtils.isEmpty(departmentId) ? "0" : departmentId;
        departmentIds.add(departmentId);
        if (departmentList.isSuccess() && departmentList.getData().getItems() != null) {
            departmentIds = departmentList.getData().getItems().stream().map(
                    DepartmentList.Department::getDepartmentId).collect(Collectors.toList());
            departmentIds.add(0, departmentId);
        }
        return departmentIds;
    }

    public UserList getUserList(String departmentId, boolean fetchChild) {
        UserList userList;
        String token = getToken();
        if (StringUtils.isEmpty(departmentId)) {
            departmentId = "0";
        }
        if (fetchChild) {
            List<String> departmentIds = getDepartmentIds(departmentId);
            userList = getUserList(departmentIds);
        } else {
            userList = getUserListByDepartmentId(token, departmentId, null, null);
        }
        return userList;
    }

    public UserList getUserList(List<String> departmentIds) {
        UserList userList = null;
        Map<String, Boolean> userIdMap = new HashMap<>();
        String token = getToken();
        for (int i = 0, count = departmentIds.size(); i < count; i++) {
            UserList currentUserList = getUserListByDepartmentId(token, departmentIds.get(i), null, null);
            if (currentUserList.isSuccess() && currentUserList.getData().getItems() != null) {
                if (userList == null) {
                    userList = currentUserList;
                    for (UserList.User user : currentUserList.getData().getItems()) {
                        userIdMap.put(user.getUserId(), true);
                    }
                } else {
                    for (UserList.User user : currentUserList.getData().getItems()) {
                        if (!userIdMap.containsKey(user.getUserId())) {
                            userIdMap.put(user.getUserId(), true);
                            userList.getData().getItems().add(user);
                        }
                    }
                }
            }
        }
        return userList;
    }

    public UserList.User getUser(UserAttr userAttr, Object value) {
        UserList.User user = null;
        if (userAttr == null || value == null || StringUtils.isEmpty(value.toString())) {
            return user;
        }

        UserDetail userDetail = null;
        UserId userId = null;
        String id = null;
        String token = getToken();
        Map<String, Object> uriVariables = new HashMap<>();
        Map<String, Object> params = new HashMap<>();
        params.put("include_resigned", false);
        switch (userAttr) {
            case ID:
                uriVariables.put("user_id", value.toString());
                userDetail = doGet(GET_USER_PATH, uriVariables, token, UserDetail.class);
                break;
            case MOBILE:
                List<String> mobiles = new ArrayList<>();
                mobiles.add(getPureMobile(value.toString()));
                params.put("mobiles", mobiles);
                userId = doPost(GET_USER_ID_PATH, params, token, UserId.class);
                break;
            case EMAIL:
                List<String> emails = new ArrayList<>();
                emails.add(value.toString());
                params.put("emails", emails);
                userId = doPost(GET_USER_ID_PATH, params, token, UserId.class);
                break;
            case EMPLOYEE_NO:
            case ENTERPRISE_EMAIL:
            default:
                break;
        }
        if (userId != null && userId.isSuccess() && userId.getData() != null &&
                userId.getData().getUserList() != null && userId.getData().getUserList().size() == 1) {
            id = userId.getData().getUserList().get(0).getUserId();
        }
        if (id != null) {
            uriVariables.put("user_id", id);
            userDetail = doGet(GET_USER_PATH, uriVariables, token, UserDetail.class);
        }
        if (userDetail != null && userDetail.isSuccess()) {
            user = userDetail.getData().getUser();
        }
        if (userAttr == UserAttr.EMPLOYEE_NO || userAttr == UserAttr.NAME || userAttr == UserAttr.ENTERPRISE_EMAIL) {
            user = findUser(getAllUsers(), userAttr, value, userAttr == UserAttr.NAME);
        }
        return user;
    }

    public List<UserList.User> getAllUsers() {
        List<UserList.User> users = null;
        if (allUsers == null) {
            UserList userList = getUserList(null, true);
            if (userList.isSuccess() && userList.getData() != null) {
                users = userList.getData().getItems();
                allUsers = users;
            }
        } else {
            users = allUsers;
        }
        return users;
    }

    public DepartmentDetail createDepartment(String name, DepartmentList.DepartmentI18nName i18nName, String parentId,
                                             String id, String order, Boolean createGroupChat) {
        String token = getToken();
        Map<String, Object> params = new HashMap<>();
        params.put("name", name);
        params.put("parent_department_id", StringUtils.isEmpty(parentId) ? "0" : parentId);
        if (i18nName != null) {
            params.put("i18n_name", i18nName);
        }
        if (!StringUtils.isEmpty(id)) {
            params.put("department_id", id);
        }
        if (!StringUtils.isEmpty(order)) {
            params.put("order", order);
        }
        if (createGroupChat != null) {
            params.put("create_group_chat", createGroupChat);
        }
        return doPost(CREATE_DEPARTMENT_PATH, params, token, DepartmentDetail.class);
    }

    public DepartmentDetail updateDepartment(String id, String name, DepartmentList.DepartmentI18nName i18nName,
                                             String parentId, String order, Boolean createGroupChat,
                                             String leaderUserId, List<DepartmentList.DepartmentLeader> leaders) {
        String token = getToken();
        Map<String, Object> params = new HashMap<>();
        if (!StringUtils.isEmpty(name)) {
            params.put("name", name);
        }
        if (i18nName != null) {
            params.put("i18n_name", i18nName);
        }
        if (!StringUtils.isEmpty(parentId)) {
            params.put("parent_department_id", parentId);
        }
        if (!StringUtils.isEmpty(order)) {
            params.put("order", order);
        }
        if (createGroupChat != null) {
            params.put("create_group_chat", createGroupChat);
        }
        if (!StringUtils.isEmpty(leaderUserId)) {
            params.put("leader_user_id", leaderUserId);
        }
        if (leaders != null) {
            params.put("leaders", leaders);
        }
        return doPatch(formatUri(UPDATE_DEPARTMENT_PATH, "department_id", id), params, token, DepartmentDetail.class);
    }

    public Response updateDepartmentId(String id, String newId) {
        String token = getToken();
        Map<String, Object> params = new HashMap<>();
        params.put("new_department_id", newId);
        return doPatch(formatUri(UPDATE_DEPARTMENT_ID_PATH, "department_id", id), params, token, Response.class);
    }

    public Response deleteDepartment(String id) {
        DepartmentList departmentList = getDepartmentList(id);
        List<String> departmentIds = getDepartmentIds(id, departmentList);
        UserList userList = getUserList(departmentIds);
        if (userList != null && userList.getData() != null && userList.getData().getItems() != null) {
            for (UserList.User user : userList.getData().getItems()) {
                List<String> currentDepartmentIds = new ArrayList<>();
                List<String> userDepartmentIds = user.getDepartmentIds();
                if (userDepartmentIds != null) {
                    for (String departmentId : userDepartmentIds) {
                        if (!departmentIds.contains(departmentId)) {
                            currentDepartmentIds.add(departmentId);
                        }
                    }
                }
                if (currentDepartmentIds.isEmpty()) {
                    currentDepartmentIds.add("0");
                }
                UserInfo userInfo = new UserInfo();
                userInfo.setUserId(user.getUserId());
                userInfo.setDepartmentIds(currentDepartmentIds);
                updateUser(userInfo);
            }
        }
        if (departmentList.isSuccess() && departmentList.getData().getItems() != null) {
            deleteSubdepartments(departmentList, id, id);
        }
        return doDelete(formatUri(DELETE_DEPARTMENT_PATH, "department_id", id), null, getToken(), Response.class);
    }

    public UserDetail createUser(UserInfo userInfo) {
        return doPost(CREATE_USER_PATH, userInfo.toCreateParams(), getToken(), UserDetail.class);
    }

    public UserDetail updateUser(UserInfo userInfo) {
        return doPatch(formatUri(UPDATE_USER_PATH, "user_id", userInfo.getUserId()), userInfo.toUpdateParams(),
                getToken(), UserDetail.class);
    }

    public Response updateUserId(String id, String newId) {
        String token = getToken();
        Map<String, Object> params = new HashMap<>();
        params.put("new_user_id", newId);
        return doPatch(formatUri(UPDATE_USER_ID_PATH, "user_id", id), params, token, Response.class);
    }

    public Response deleteUser(String id) {
        return doDelete(formatUri(DELETE_USER_PATH, "user_id", id), null, getToken(), Response.class);
    }

    public Response resurrectUser(String id) {
        Map<String, Object> params = new HashMap<>();
        return doPost(formatUri(RESURRECT_USER_PATH, "user_id", id), params, getToken(), Response.class);
    }

    public void removeCircularReference(String userId, String leaderId) {
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(leaderId)) {
            return;
        }

        List<UserList.User> users = getAllUsers();
        UserList.User user = findUser(users, UserAttr.ID, userId);
        if (user == null) {
            return;
        }

        UserList.User leader;
        while (true) {
            leader = findUser(users, UserAttr.ID, leaderId);
            if (leader == null || StringUtils.isEmpty(leader.getLeaderUserId())) {
                break;
            }
            if (leader.getLeaderUserId().equals(userId)) {
                UserInfo userInfo = new UserInfo();
                userInfo.setUserId(leaderId);
                userInfo.setLeaderUserId("");
                updateUser(userInfo);
                break;
            }
            leaderId = leader.getLeaderUserId();
        }
    }

    public <T> T doGet(String uri, Map<String, Object> uriVariables, String token, Class<T> clazz) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        headers.add(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", token));
        return doGet(API_HOST + uri, headers, uriVariables, clazz);
    }

    public <T> T doPost(String uri, Map<String, Object> params, String token, Class<T> clazz) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        if (!StringUtils.isEmpty(token)) {
            headers.setBearerAuth(token);
        }
        return doPost(API_HOST + uri, headers, params, clazz);
    }

    public <T> T doPatch(String uri, Map<String, Object> params, String token, Class<T> clazz) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        if (!StringUtils.isEmpty(token)) {
            headers.setBearerAuth(token);
        }
        return doPatch(API_HOST + uri, headers, params, clazz);
    }

    public <T> T doDelete(String uri, Map<String, Object> uriVariables, String token, Class<T> clazz) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        headers.add(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", token));
        return doDelete(API_HOST + uri, headers, uriVariables, clazz);
    }

    @Override
    protected boolean checkUser(UserList.User user, UserAttr userAttr, Object value) {
        boolean result = false;
        switch (userAttr) {
            case ID:
                if (user.getUserId().equals(value)) {
                    result = true;
                }
                break;
            case EMPLOYEE_NO:
                if (user.getEmployeeNo() != null && user.getEmployeeNo().equals(value)) {
                    result = true;
                }
                break;
            case NAME:
                if (user.getName().equalsIgnoreCase(value.toString())) {
                    result = true;
                }
                break;
            case MOBILE:
                result = compareMobile(user.getMobile(), (String) value);
                break;
            case EMAIL:
                if (user.getEmail() != null && user.getEmail().equalsIgnoreCase(value.toString())) {
                    result = true;
                }
                break;
            case ENTERPRISE_EMAIL:
                if (user.getEnterpriseEmail() != null && user.getEnterpriseEmail().equalsIgnoreCase(value.toString())) {
                    result = true;
                }
                break;
            default:
                break;
        }
        return result;
    }

    private DepartmentList getDepartmentList(String token, String departmentId, String pageToken,
                                             DepartmentList departmentList) {
        departmentId = StringUtils.isEmpty(departmentId) ? "0" : departmentId;
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("department_id", departmentId);
        uriVariables.put("page_token", StringUtils.isEmpty(pageToken) ? "" : pageToken);
        DepartmentList currentDepartmentList = doGet(GET_DEPARTMENT_LIST_PATH, uriVariables, token,
                DepartmentList.class);
        if (departmentList == null) {
            departmentList = currentDepartmentList;
        } else if (currentDepartmentList.getData() != null && currentDepartmentList.getData().getItems() != null) {
            departmentList.getData().getItems().addAll(currentDepartmentList.getData().getItems());
        }
        if (currentDepartmentList.isSuccess() && currentDepartmentList.getData() != null &&
                currentDepartmentList.getData().getHasMore() != null && currentDepartmentList.getData().getHasMore() &&
                currentDepartmentList.getData().getPageToken() != null) {
            getDepartmentList(token, departmentId, currentDepartmentList.getData().getPageToken(), departmentList);
        }
        return departmentList;
    }

    private void generateOrganizationTree(List<TreeData> treeDataList, List<DepartmentList.Department> departments,
                                          TreeData parent) {
        for (int i = 0, count = departments.size(); i < count; i++) {
            DepartmentList.Department department = departments.get(i);
            if (department.getParentDepartmentId().equals(parent.getKey())) {
                TreeData item = new TreeData();
                item.setKey(department.getDepartmentId());
                item.setTitle(department.getName());
                if (parent.getChildren() == null) {
                    parent.setChildren(new ArrayList<>());
                }
                parent.getChildren().add(item);
                generateOrganizationTree(treeDataList, departments, item);
            }
        }
    }

    private UserList getUserListByDepartmentId(String token, String departmentId, String pageToken, UserList userList) {
        if (StringUtils.isEmpty(departmentId)) {
            departmentId = "0";
        }
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("department_id", departmentId);
        uriVariables.put("page_token", StringUtils.isEmpty(pageToken) ? "" : pageToken);
        UserList currentUserList = doGet(GET_USER_LIST_PATH, uriVariables, token, UserList.class);
        if (userList == null) {
            userList = currentUserList;
        } else if (currentUserList.getData() != null && currentUserList.getData().getItems() != null) {
            userList.getData().getItems().addAll(currentUserList.getData().getItems());
        }
        if (currentUserList.isSuccess() && currentUserList.getData() != null &&
                currentUserList.getData().getHasMore() != null && currentUserList.getData().getHasMore() &&
                currentUserList.getData().getPageToken() != null) {
            getUserListByDepartmentId(token, departmentId, currentUserList.getData().getPageToken(), userList);
        }
        return userList;
    }

    private void deleteSubdepartments(DepartmentList departmentList, String parentId, String rootId) {
        List<String> ids = new ArrayList<>();
        for (DepartmentList.Department department : departmentList.getData().getItems()) {
            if (department.getParentDepartmentId() != null && department.getParentDepartmentId().equals(parentId)) {
                ids.add(department.getDepartmentId());
            }
        }
        for (String id : ids) {
            deleteSubdepartments(departmentList, id, rootId);
        }
        if (!parentId.equals(rootId)) {
            doDelete(formatUri(DELETE_DEPARTMENT_PATH, "department_id", parentId), null, getToken(), Response.class);
        }
    }

}
